/* 
 * File:   Sesion.cpp
 * Author: Maximiliano Monzon (82341)
 * 
 * Created on October 18, 2012, 7:00 PM
 */

#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Sesion.h"
#include "Thread.h"
#include "ProtocoloConstantes.h"

using namespace TP::Threading;

using namespace std;
using namespace TP::Comunicacion;

#define CONEXION_FINALIZADA 0
#define CONEXION_ERROR -1

Sesion::Sesion(int pFileDescriptor, struct sockaddr_in* pClientAddress){
    
    this->fileDescriptor = pFileDescriptor;
    this->clientAddress = pClientAddress;
}

Sesion::~Sesion(){
    if (this->clientAddress)
        delete this->clientAddress;
}

void Sesion::agregarMensajeEntranteManejador(IMensajeEntranteManejador* pManejador){
    if (pManejador)
        this->mensajeSesionManejadores.push_back(pManejador);
}

void Sesion::agregarManejadorSesionEvento (IManejadorSesionEvento* pManejador){
    if (pManejador)
        this->manejadorSesionEvento = pManejador;
}

void Sesion::Iniciar() throw (Excepcion&){
    
    try
    {
        this->conexionFinalizada = false;
        
        // Inicio la tarea de la sesion.
        Thread::Iniciar();
    }
    catch (Excepcion& pEx)
    {
        throw;
    }

}

void Sesion::Finalizar() throw (Excepcion&){
    try
    {
        // Cierro la conexion en caso de que se encuentre activa.        
        if (!this->conexionFinalizada)
        {
            this->conexionFinalizada = true;
            shutdown(this->fileDescriptor, SHUT_RDWR );
        }
        
        // Finalizo las operaciones.
        Thread::Finalizar();
    }
    catch (Excepcion& pEx)
    {
        throw;
    }
}

void Sesion::Responder (const string& pMensaje) throw (Excepcion&) {
    
    // Codifico el mensaje.
    string msg_snd = pMensaje;
    
    for (unsigned int i = 0; i < msg_snd.size(); i++)
    {
        switch (msg_snd[i])
        {
            case '\n':
                msg_snd[i] = SALTO_LINEA;
        }
    }
    
    msg_snd += FIN_MENSAJE;
    int n = send(this->fileDescriptor, msg_snd.c_str(), msg_snd.size(), 0);
    
    if (n == -1) 
        throw Excepcion ("ERROR writing to socket");

    else if (n == 0)
        throw Excepcion ("Conexion perdida.");
}

void Sesion::tarea(){
    
    /* La sesion tiene como tarea recibir los mensajes entrantes, desencodearlos
     * y notificar el mensaje a sus suscriptores.
     * 
     * La tarea es valida siempre y cuando la conexion no haya sido finaliada
     * por el host.
     */
    
    string mensaje = "";
    char buffer [256];
    int bytes_leidos = -1;
    int flags = 0;
    this->errorConexion = false;

    while (!this->conexionFinalizada && !this->errorConexion)
    {
        // Inicializo el buffer para lectura.
        bzero(buffer,256);

        bytes_leidos = recv (this->fileDescriptor, buffer, 255, flags);

        if (bytes_leidos <= 0)
        {
            this->manejarErrorComunicacion(bytes_leidos);
            continue;
        }

        for (int i = 0; i < bytes_leidos; i++){

            if (FIN_MENSAJE == buffer[i])
            {
                // Notifico el mensaje a los servicios.
                for (vector<IMensajeEntranteManejador*>::iterator it = this->mensajeSesionManejadores.begin(); it != this->mensajeSesionManejadores.end(); ++it)
                    (*it)->mensajeEntranteManejador((IMensajero*) this, mensaje);                

                mensaje = "";
            }
            else if ('\r' == buffer[i] || '\n' == buffer[i])
                int a = 0; // @todo: Eliminar el ese if cuando pase a produccion.  Se encuentra para compatibilidad con telnet en tiempo de desarrollo/testing.
            else
                mensaje += buffer[i];
        }
    }
}

void Sesion::manejarErrorComunicacion (int pEstadoComunicacion){

    if (CONEXION_FINALIZADA == pEstadoComunicacion)
    {
        // Cierro el descriptor.
        close (this->fileDescriptor);
        
        // Configuro el flag acorde a situacion.
        this->conexionFinalizada = true;
    }
    else if (CONEXION_ERROR == pEstadoComunicacion)
    {
        if (!this->conexionFinalizada)
        {
            string msg = "Error de lectura del host ";
            msg += inet_ntoa(this->clientAddress->sin_addr);
            msg += ".";

            this->manejadorSesionEvento->eventoErrorComunicacion((IEnviante*)this, msg);
        }
        this->errorConexion = true;
    }
}

